package com.ouc.oceanBase.service.impl;


import com.ouc.common.constant.VariableName;
import com.ouc.common.entity.NcData;
import com.ouc.common.ncRead.NcDao;
import com.ouc.common.utils.Arith;
import com.ouc.common.utils.ArrayUtils;
import com.ouc.common.utils.MathUtils;
import com.ouc.common.utils.file.FileUtils;
import com.ouc.common.utils.nc.NcUtil;
import com.ouc.oceanBase.entity.ContourData;
import com.ouc.oceanBase.entity.FlowGridData;
import com.ouc.oceanBase.service.EddyCharacterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static com.ouc.common.constant.NcConstants.UU;
import static com.ouc.common.constant.NcConstants.VV;
import static java.lang.Float.NaN;

@Service
public class EddyCharacterServiceImpl implements EddyCharacterService {

  @Autowired
  private NcDao ncDao;

  @Override
  public ContourData<FlowGridData> getFlowGridData(NetcdfFile ncFile, Integer level) throws IOException, InvalidRangeException {
    Variable u = ncDao.getVariable(ncFile, VariableName.UFLOW);
    Variable v = ncDao.getVariable(ncFile, VariableName.VFLOW);
    if (u == null || v == null) {
      return null;
    }
    Variable lon = ncDao.getVariable(ncFile, VariableName.LON);
    Variable lat = ncDao.getVariable(ncFile, VariableName.LAT);
    List<Variable> variables = Arrays.asList(u, v, lon, lat);
    Map<String, NcData> map = NcUtil.getAllDataOfSlice(variables, level);
    FlowGridData flowGridData = new FlowGridData();
    float[] uuData = null;
    float[] vvData = null;
    for (Map.Entry<String, NcData> entry : map.entrySet()) {
      if (entry.getKey().matches(".*lon.*")) {
        flowGridData.setListLon(entry.getValue().getData1D());
        continue;
      }
      if (entry.getKey().matches(".*lat.*")) {
        flowGridData.setListLat(ArrayUtils.arr1DDilution(entry.getValue().getData1D(), 2));
        continue;
      }
      if (entry.getKey().matches(".*u.*")) {
        uuData = ArrayUtils.flat2D(ArrayUtils.arr2DDilution(entry.getValue().getData2D(), 2, 0));
        continue;
      }
      if (entry.getKey().matches(".*v.*")) {
        vvData = ArrayUtils.flat2D(ArrayUtils.arr2DDilution(entry.getValue().getData2D(), 2, 0));
        continue;
      }
    }
    float[] values = new float[uuData.length];
    for (int i = 0; i < uuData.length; i++) {
      values[i] = (float) Math.sqrt(Math.pow(uuData[i], 2) + Math.pow(vvData[i], 2));
    }
    flowGridData.setListUFlow(uuData);
    flowGridData.setListVFlow(vvData);
    flowGridData.setValues(values);
    flowGridData.setVariableName("flow");

    float[] minAndMax = MathUtils.minAndMax(values);
    float[] listLon = flowGridData.getListLon();
    int lonLength = listLon.length;
    float lonInterval = BigDecimal.valueOf(listLon[1]).subtract(BigDecimal.valueOf(listLon[0])).floatValue();
    float startLon = listLon[0];
    float[] listLat = flowGridData.getListLat();
    int latLength = listLat.length;
    float latInterval = BigDecimal.valueOf(listLat[1]).subtract(BigDecimal.valueOf(listLat[0])).floatValue();
    float startLat = listLat[0];

    return new ContourData<>(flowGridData, minAndMax[0], minAndMax[1],
        lonLength, latLength, lonInterval, latInterval, startLon, startLat, flowGridData.getVariableName());
  }

  @Override
  public ContourData<FlowGridData> getFlowGridData(NetcdfFile ncFile, Float[] longitude, Float[] latitude, Integer level, Integer time) throws IOException, InvalidRangeException {
    Variable u = ncDao.getVariable(ncFile, VariableName.UFLOW);
    Variable v = ncDao.getVariable(ncFile, VariableName.VFLOW);
    if (u == null || v == null) {
      return null;
    }
    FlowGridData flowGridData = new FlowGridData();
    Map<String, NcData> ncDataOfLimitMap = ncDao.getNcDataOfLimit(ncFile, longitude, latitude, level, time);
    float[] uuData = ArrayUtils.flat2D(ncDataOfLimitMap.get(u.getShortName()).getData2D());
    float[] vvData = ArrayUtils.flat2D(ncDataOfLimitMap.get(v.getShortName()).getData2D());
    float[] values = new float[uuData.length];
    for (int i = 0; i < values.length; i++) {
      values[i] = (float) Math.sqrt(Math.pow(uuData[i], 2) + Math.pow(vvData[i], 2));
    }
    flowGridData.setListLat(ncDataOfLimitMap.get(ncDao.getVariable(ncFile, VariableName.LAT).getShortName()).getData1D());
    flowGridData.setListLon(ncDataOfLimitMap.get(ncDao.getVariable(ncFile, VariableName.LON).getShortName()).getData1D());
    flowGridData.setListUFlow(uuData);
    flowGridData.setListVFlow(vvData);
    flowGridData.setValues(values);
    flowGridData.setVariableName("flow");
    return getFlowContourData(flowGridData, values);

  }

  @Override
  public ContourData<FlowGridData> getFlowGridDataWithWind(String fileName, Float[] longitude, Float[] latitude, Integer level, Integer time) throws IOException, InvalidRangeException {
    NetcdfFile ncFile = NetcdfFile.open(fileName);
    Variable u = ncDao.getVariable(ncFile, VariableName.WINDU);
    Variable v = ncDao.getVariable(ncFile, VariableName.WINDV);
    if (u == null || v == null) {
      return null;
    }
    FlowGridData flowGridData = new FlowGridData();
    Map<String, NcData> ncDataOfLimitMap = ncDao.getNcDataOfLimit(ncFile, longitude, latitude, level, time);
    float[] uuData = ArrayUtils.flat2D(ncDataOfLimitMap.get(u.getShortName()).getData2D());
    float[] vvData = ArrayUtils.flat2D(ncDataOfLimitMap.get(v.getShortName()).getData2D());
    float[] values = new float[uuData.length];
    for (int i = 0; i < values.length; i++) {
      values[i] = (float) Math.sqrt(Math.pow(uuData[i], 2) + Math.pow(vvData[i], 2));
    }
    flowGridData.setListLat(ncDataOfLimitMap.get(ncDao.getVariable(ncFile, VariableName.LAT).getShortName()).getData1D());
    flowGridData.setListLon(ncDataOfLimitMap.get(ncDao.getVariable(ncFile, VariableName.LON).getShortName()).getData1D());
    flowGridData.setListUFlow(uuData);
    flowGridData.setListVFlow(vvData);
    flowGridData.setValues(values);
    flowGridData.setVariableName("flow");
    return getFlowContourData(flowGridData, values);
  }

  @Override
  public ContourData<FlowGridData> getFlowGridDataFormTwoFile(String filePath, Float[] longitude, Float[] latitude, Integer level, Integer time, Integer index) throws IOException, InvalidRangeException {
    NetcdfFile ncFile = NetcdfFile.open(filePath);
    FlowGridData flowGridData = new FlowGridData();
    File file = new File(filePath);
    String otherPath;
    String fileName = FileUtils.getName(filePath);

    if (ncDao.getVariable(ncFile, VariableName.UFLOW) != null) {
      otherPath = file.getParentFile().getAbsolutePath() + File.separator + "v" + fileName.substring(index);
      NetcdfFile ncFileV = NetcdfFile.open(otherPath);
      Map<String, NcData> ncDataMapU = ncDao.getNcDataOfLimitFromVariable(ncFile, UU, longitude, latitude, level, time);
      Map<String, NcData> ncDataMapV = ncDao.getNcDataOfLimitFromVariable(ncFileV, VV, longitude, latitude, level, time);
      Variable u = ncDao.getVariable(ncFile, VariableName.UFLOW);
      Variable v = ncDao.getVariable(ncFileV, VariableName.VFLOW);

      float[] uuData = ArrayUtils.flat2D(ncDataMapU.get(u.getShortName()).getData2D());
      float[] vvData = ArrayUtils.flat2D(ncDataMapV.get(v.getShortName()).getData2D());
      float[] values = new float[uuData.length];
      for (int i = 0; i < values.length; i++) {
        values[i] = (float) Math.sqrt(Math.pow(uuData[i], 2) + Math.pow(vvData[i], 2));
      }
      flowGridData.setListLat(ncDataMapU.get(ncDao.getVariable(ncFile, VariableName.LAT).getShortName()).getData1D());
      flowGridData.setListLon(ncDataMapU.get(ncDao.getVariable(ncFile, VariableName.LON).getShortName()).getData1D());
      flowGridData.setListUFlow(uuData);
      flowGridData.setListVFlow(vvData);
      flowGridData.setValues(values);
      flowGridData.setVariableName("flow");
      return getFlowContourData(flowGridData, values);

    } else if (ncDao.getVariable(ncFile, VariableName.VFLOW) != null) {
      otherPath = file.getParentFile().getAbsolutePath() + File.separator + "v" + fileName.substring(index);
      NetcdfFile ncFileU = NetcdfFile.open(otherPath);
      Map<String, NcData> ncDataMapV = ncDao.getNcDataOfLimitFromVariable(ncFile, VV, longitude, latitude, level, time);
      Map<String, NcData> ncDataMapU = ncDao.getNcDataOfLimitFromVariable(ncFileU, UU, longitude, latitude, level, time);
      Variable u = ncDao.getVariable(ncFile, VariableName.UFLOW);
      Variable v = ncDao.getVariable(ncFileU, VariableName.VFLOW);
      float[] uuData = ArrayUtils.flat2D(ncDataMapU.get(u.getShortName()).getData2D());
      float[] vvData = ArrayUtils.flat2D(ncDataMapV.get(v.getShortName()).getData2D());
      float[] values = new float[uuData.length];
      for (int i = 0; i < values.length; i++) {
        values[i] = (float) Math.sqrt(Math.pow(uuData[i], 2) + Math.pow(vvData[i], 2));
      }
      flowGridData.setListLat(ncDataMapU.get(ncDao.getVariable(ncFile, VariableName.LAT).getShortName()).getData1D());
      flowGridData.setListLon(ncDataMapU.get(ncDao.getVariable(ncFile, VariableName.LON).getShortName()).getData1D());
      flowGridData.setListUFlow(uuData);
      flowGridData.setListVFlow(vvData);
      flowGridData.setValues(values);
      flowGridData.setVariableName("flow");
      return getFlowContourData(flowGridData, values);
    } else {
      return null;
    }
  }

  @Override
  public ContourData<FlowGridData> getFlowGridData(NetcdfFile ncFile, Float[] longitude, Float[] latitude, Integer level) throws IOException, InvalidRangeException {
    return getFlowGridData(ncFile, longitude, latitude, level, null);
  }

  @Override
  public ContourData<FlowGridData> getFlowGridDataFromDegree(NetcdfFile ncFile, Float[] longitude, Float[] latitude, Integer level) throws IOException, InvalidRangeException {
    return getFlowGridDataFromDegree(ncFile, longitude, latitude, level, null);
  }

  @Override
  public ContourData<FlowGridData> getFlowGridDataFromDegree(NetcdfFile ncFile, Float[] longitude, Float[] latitude, Integer level, Integer time) throws IOException, InvalidRangeException {
    Variable degreeVariable = ncDao.getVariable(ncFile, VariableName.WAVEDIRECTION);
    Variable valueVariable = ncDao.getVariable(ncFile, VariableName.WAVEHEIGHT);
    if (degreeVariable == null || valueVariable == null) {
      return null;
    }
    String degreeName = degreeVariable.getShortName();
    String valueName = valueVariable.getShortName();
    FlowGridData flowGridData = new FlowGridData();
    Map<String, NcData> ncDataOfDegree = ncDao.getNcDataWithLimit(ncFile, degreeName, longitude, latitude, level, time);
    Map<String, NcData> ncDataOfValue = ncDao.getNcDataWithLimit(ncFile, valueName, longitude, latitude, level, time);
    float[] degreeFloat = ArrayUtils.flat2D(ncDataOfDegree.get(degreeName).getData2D());
    float[] valueFloat = ArrayUtils.flat2D(ncDataOfValue.get(valueName).getData2D());
    float[] uuData = new float[valueFloat.length];
    float[] vvData = new float[valueFloat.length];
    for (int i = 0; i < valueFloat.length; i++) {
      /**
       * 保留五位小数
       */
      if (Float.isNaN(valueFloat[i]) || Float.isNaN(degreeFloat[i])) {
        uuData[i] = NaN;
        vvData[i] = NaN;
      } else {
        uuData[i] = (float) Arith.round(BigDecimal.valueOf(valueFloat[i]).multiply(BigDecimal.valueOf(Math.cos(Math.toRadians(degreeFloat[i])))).doubleValue(), 5);
        vvData[i] = (float) Arith.round(BigDecimal.valueOf(valueFloat[i]).multiply(BigDecimal.valueOf(Math.sin(Math.toRadians(degreeFloat[i])))).doubleValue(), 5);
      }
    }
    flowGridData.setListLat(ncDataOfValue.get("lon").getData1D());
    flowGridData.setListLon(ncDataOfValue.get("lat").getData1D());
    flowGridData.setValues(valueFloat);
    flowGridData.setVariableName("flow");
    flowGridData.setListUFlow(uuData);
    flowGridData.setListVFlow(vvData);
    return getFlowContourData(flowGridData, valueFloat);
  }

  /**
   * 为了代码的美观简洁（避免重复代码）
   * 适用于获取流数据的情况
   *
   * @param flowGridData 流格网数据
   * @param valueFloat   u和v的总值
   * @return
   */
  private ContourData<FlowGridData> getFlowContourData(FlowGridData flowGridData, float[] valueFloat) {
    ContourData<FlowGridData> contourData;
    float[] minAndMax = MathUtils.minAndMax(valueFloat);
    float[] listLon = flowGridData.getListLon();
    float lonInterval = BigDecimal.valueOf(listLon[1]).subtract(BigDecimal.valueOf(listLon[0])).floatValue();
    float[] listLat = flowGridData.getListLat();
    float latInterval = BigDecimal.valueOf(listLat[1]).subtract(BigDecimal.valueOf(listLat[0])).floatValue();
    contourData = new ContourData<>(flowGridData, minAndMax[0], minAndMax[1],
        listLon.length, listLat.length,
        lonInterval, latInterval, listLon[0], listLat[0], flowGridData.getVariableName());

    return contourData;

  }
}
